package com.sys.utils;

import com.alibaba.fastjson.JSONObject;
import com.bhudy.base.QueryFilter;
import com.sys.entity.SysPlugin;
import com.sys.entity.SysUser;
import com.bhudy.exception.SysException;
import com.bhudy.exception.SysExceptionCode;
import com.sys.service.SysPluginService;
import com.sys.service.SysUserService;
import com.bhudy.utils.ResponseResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.json.XML;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.*;

/**
 * Created by Administrator on 2019/8/23/023.
 */
public final class WeChatUtils {

    private WeChatUtils() {
    }

    private final static SysUserService sysUserService = Utils.getBean(SysUserService.class);
    private final static SysPluginService sysPluginService = Utils.getBean(SysPluginService.class);

    private final static String tasWorkOrder = "M2QftA15MJv-Wiee3Tlq-Fo1Hn5oyU101FnT4_yam8A"; // 工单状态通知Key
    private final static String tasProduct = "2QOXhinpfTZIm26e_WQYTLda2lQQeB-MU0J548hiPjk"; // 产品变更通知
    private final static String tasOrderRateOfProgress = "a0EaDxQNXGiRW90lxdB1vwCaRrbHsqSB-7nvj9Dash4";

    private final static String authorizationCode = "authorization_code"; // 微信调用接口使用参数authorization_code
    private final static String clientCredential = "client_credential"; // 微信调用接口使用参数client_credential

    private static Map<String, Object> weChatTokenMap = null;
    private final static Map<String, String> accessTokenMap = new HashMap<>();

    private static boolean isType = false;

    /**
     * 获取公众号的appID和appSecret
     */
    public static Map<String, String> getTasKeyMap() {
        /*Stringa gzhAppId = "wxfc60c6f79355c63a"; // 公众号appid
        Stringa gzhAppSecret = "b0d12142ae97ba5a031b105fa27706e9"; // 公众号AppSecret 0l2N9502pJHDR/PUjtLgfR0huErwbYo9nrJVGlUMLioOOasieZ0AHSCOl6HylYpP*/
        return sysPluginService.getSysPluginDataByType(SysPlugin.TENCENT_TAS);
    }

    /**
     * 获取小程序的appID和appSecret
     */
    public static Map<String, String> getAppletKeyMap() {
        /*Stringa appID = "wx157278de2a7478d9"; // 小程序appid
        Stringa appSecret = "926563146e4873575554c303d4f64061";// 小程序AppSecret*/
        return sysPluginService.getSysPluginDataByType(SysPlugin.TENCENT_APPLET);
    }

    /**
     * 发送报告生成通知
     * 报告生成通知Id: qz_vzebkH1j8DZEI2KLvOtFsxz0bhtWSZRlHtzcgAF8
     * <p>
     * <p>
     * 标题：{{first.DATA}}
     * 工单号：{{keyword1.DATA}}
     * 派工时间：{{keyword2.DATA}}
     * 说明：{{remark.DATA}}
     *
     * @return 是否发送成功
     */
    public static void sendTasWorkOrder(String tasOpenid, Map<String, Object> keywordMap) {
        sendTas(tasOpenid, WeChatUtils.tasWorkOrder, String.valueOf(keywordMap.get("first")), String.valueOf(keywordMap.get("remark")), "?url=/pages/order/order", keywordMap);
    }

    /**
     * 产品变更通知
     * <p>
     * 标题：{{first.DATA}}
     * 变更类型：{{keyword1.DATA}}
     * 变更结果：{{keyword2.DATA}}
     * 说明：{{remark.DATA}}
     *
     * @param tasOpenid
     * @param keywordMap
     */
    public static void sendTasProduct(String tasOpenid, Map<String, Object> keywordMap) {
        sendTas(tasOpenid, WeChatUtils.tasProduct, String.valueOf(keywordMap.get("first")), String.valueOf(keywordMap.get("remark")), null, keywordMap);
    }

    /**
     * 工单进度通知
     * <p>
     * 标题：{{first.DATA}}
     * 工单编号：{{keyword1.DATA}}
     * 工单标题：{{keyword2.DATA}}
     * 工单状态：{{keyword2.DATA}}
     * 工单处理人：{{keyword2.DATA}}
     * 说明：{{remark.DATA}}
     *
     * @param tasOpenid
     * @param keywordMap
     */
    public static void sendTasOrderRateOfProgress(String tasOpenid, Map<String, Object> keywordMap) {
        sendTas(tasOpenid, WeChatUtils.tasOrderRateOfProgress, String.valueOf(keywordMap.get("first")), String.valueOf(keywordMap.get("remark")), "?url=/pages/order/order", keywordMap);
    }

    /**
     * 发送微信公众号信息
     * <p>
     * 参数	是否必填	说明
     * touser	是	接收者openid
     * template_id	是	模板ID
     * url	否	模板跳转链接（海外帐号没有跳转能力）
     * miniprogram	否	跳小程序所需数据，不需跳小程序可不用传该数据
     * appid	是	所需跳转到的小程序appid（该小程序appid必须与发模板消息的公众号是绑定关联关系，暂不支持小游戏）
     * pagepath	否	所需跳转到小程序的具体页面路径，支持带参数,（示例index?foo=bar），要求该小程序已发布，暂不支持小游戏
     * data	是	模板数据
     * color	否	模板内容字体颜色，不填默认为黑色
     * <p>
     * json数据模板
     * {"touser":"OPENID","template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY","url":"http://weixin.qq.com/download",
     * "miniprogram":{"appid":"xiaochengxuappid12345","pagepath":"index?foo=bar"},"data":{"first":{"value":"恭喜你购买成功！","color":"#173177"},
     * "keyword1":{"value":"巧克力","color":"#173177"},"keyword2":{"value":"39.8元","color":"#173177"},"keyword3":{"value":"2014年9月22日","color":"#173177"},
     * "remark":{"value":"欢迎再次购买！","color":"#173177"}}}
     *
     * @param openId     接收消息的用户openid
     * @param templateId 消息模板id
     * @param first      标题
     * @param remark     结尾
     * @param keywordMap 内容map
     * @return 是否发送成功
     */
    public static void sendTas(String openId, String templateId, String first, String remark, String path, Map<String, Object> keywordMap) {
        if (openId == null || openId.equals("")) return;

        String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + WeChatUtils.getToken();
        Map<String, Object> map = new HashMap<>();
        map.put("touser", openId);
        map.put("template_id", templateId);
        // map.put("url", "http://bhudy.imwork.net");

        if (path != null) {
            Map<String, Object> miniprogramMap = new HashMap<>();
            miniprogramMap.put("appid", WeChatUtils.getAppletKeyMap().get(SysPlugin.appId));
            miniprogramMap.put("pagepath", "pages/togo/togo" + (path == null ? "?url=/pages/index/index" : path));
            map.put("miniprogram", miniprogramMap);
        }

        Map<String, Object> dataMap = new HashMap<>();

        Map firstMap = new HashMap<>();
        firstMap.put("value", first);
        dataMap.put("first", firstMap);

        for (Map.Entry<String, Object> entrySet : keywordMap.entrySet()) {
            if (entrySet.getKey().equals("first") || entrySet.getKey().equals("remark")) continue;
            Map keyword1Map = new HashMap<>();
            keyword1Map.put("value", entrySet.getValue());
            dataMap.put(entrySet.getKey(), keyword1Map);
        }

        Map remarkMap = new HashMap<>();
        remarkMap.put("value", remark);
        dataMap.put("remark", remarkMap);

        map.put("data", dataMap);

        WeChatUtils.wxReqDataPost(url, JSONObject.toJSONString(map));
    }

    /**
     * 获取用户基本信息(UnionID机制)
     * 在关注者与公众号产生消息交互后，公众号可获得关注者的OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的。对于不同公众号，同一用户的openid不同）。公众号可通过本接口来根据OpenID获取用户基本信息，包括昵称、头像、性别、所在城市、语言和关注时间。
     * 请注意，如果开发者有在多个公众号，或在公众号、移动应用之间统一用户帐号的需求，需要前往微信开放平台（open.weixin.qq.com）绑定公众号后，才可利用UnionID机制来满足上述需求。
     * <p>
     * 参数	是否必须	说明
     * access_token	是	调用接口凭证
     * openid	是	普通用户的标识，对当前公众号唯一
     * lang	否	返回国家地区语言版本，zh_CN 简体，zh_TW 繁体，en 英语
     * ***********************************
     *
     * @return <br>
     * 参数	说明
     * subscribe	用户是否订阅该公众号标识，值为0时，代表此用户没有关注该公众号，拉取不到其余信息。
     * openid	用户的标识，对当前公众号唯一
     * nickname	用户的昵称
     * sex	用户的性别，值为1时是男性，值为2时是女性，值为0时是未知
     * city	用户所在城市
     * country	用户所在国家
     * province	用户所在省份
     * language	用户的语言，简体中文为zh_CN
     * headimgurl	用户头像，最后一个数值代表正方形头像大小（有0、46、64、96、132数值可选，0代表640*640正方形头像），用户没有头像时该项为空。若用户更换头像，原有头像URL将失效。
     * subscribe_time	用户关注时间，为时间戳。如果用户曾多次关注，则取最后关注时间
     * unionid	只有在用户将公众号绑定到微信开放平台帐号后，才会出现该字段。
     * remark	公众号运营者对粉丝的备注，公众号运营者可在微信公众平台用户管理界面对粉丝添加备注
     * groupid	用户所在的分组ID（兼容旧的用户分组接口）
     * tagid_list	用户被打上的标签ID列表
     * subscribe_scene	返回用户关注的渠道来源，ADD_SCENE_SEARCH 公众号搜索，ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移，ADD_SCENE_PROFILE_CARD 名片分享，ADD_SCENE_QR_CODE 扫描二维码，ADD_SCENEPROFILE LINK 图文页内名称点击，ADD_SCENE_PROFILE_ITEM 图文页右上角菜单，ADD_SCENE_PAID 支付后关注，ADD_SCENE_OTHERS 其他
     * qr_scene	二维码扫码场景（开发者自定义）
     * qr_scene_str	二维码扫码场景描述（开发者自定义）
     * UnionID
     */
    public static Map<String, Object> getUserInfo(String openId, boolean isType) {
        String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + WeChatUtils.getToken(isType) + "&openid=" + openId + "&lang=zh_CN";
        Map resultMap = WeChatUtils.wxReqDataGet(url);
        return resultMap;
    }

    public static Map<String, Object> getOpenIdListMap() {
        return getOpenIdListMap(null);
    }

    /**
     * 获取公众号用户列表
     * 公众号可通过本接口来获取帐号的关注者列表，关注者列表由一串OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。一次拉取调用最多拉取10000个关注者的OpenID，可以通过多次拉取的方式来满足需求。
     * <p>
     * 参数	是否必须	说明
     * access_token	是	调用接口凭证
     * next_openid	是	第一个拉取的OPENID，不填默认从头开始拉取
     *
     * @param nextOpenid 第一个拉取的OPENID，不填默认从头开始拉取
     * @return <br>
     * 参数    说明
     * total	关注该公众账号的总用户数
     * count	拉取的OPENID个数，最大值为10000
     * data	列表数据，OPENID的列表
     * next_openid	拉取列表的最后一个用户的OPENID
     */
    public static Map<String, Object> getOpenIdListMap(String nextOpenid) {
        // Stringa url = "https://api.weixin.qq.com/cgi-bin/user/getResMonitorItemEndpointList?access_token=" + getToken();
        String url = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + getToken();
        if (nextOpenid != null) {
            url += "&next_openid=" + nextOpenid;
        }

        Map resultMap = WeChatUtils.wxReqDataGet(url);
        return resultMap;
    }

    /**
     * 创建小程序带参数的二维码
     * 参数说明
     * <p>
     * 参数	说明
     * expire_seconds	该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填，则默认有效期为30秒。
     * action_name	二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值
     * action_info	二维码详细信息
     * scene_id	场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
     * scene_str	场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
     *
     * @return 参数    说明
     * ticket	获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。
     * expire_seconds	该二维码有效时间，以秒为单位。 最大不超过2592000（即30天）。
     * url	二维码图片解析后的地址，开发者可根据该地址自行生成需要的二维码图片
     */
    public static String createAppletAqrCode(String path) {
        String token = getToken(true);
        String url = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=" + token;

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("access_token", token);
        dataMap.put("path", path);
        dataMap.put("width", 430);

        return getReqImgBase64(url, dataMap, "POST");
    }

    /**
     * 创建公众号带参数的二维码
     * 参数说明
     * <p>
     * 参数	说明
     * expire_seconds	该二维码有效时间，以秒为单位。 最大不超过2592000（即30天），此字段如果不填，则默认有效期为30秒。
     * action_name	二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值
     * action_info	二维码详细信息
     * scene_id	场景值ID，临时二维码时为32位非0整型，永久二维码时最大值为100000（目前参数只支持1--100000）
     * scene_str	场景值ID（字符串形式的ID），字符串类型，长度限制为1到64
     *
     * @return 参数    说明
     * ticket	获取的二维码ticket，凭借此ticket可以在有效时间内换取二维码。
     * expire_seconds	该二维码有效时间，以秒为单位。 最大不超过2592000（即30天）。
     * url	二维码图片解析后的地址，开发者可根据该地址自行生成需要的二维码图片
     */
    public static String createTasQrcode() {
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getToken();
        // {"expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("expire_seconds", 60 * 60 * 24);
        dataMap.put("action_name", "QR_STR_SCENE");
        Map<String, Object> sceneMap = new HashMap<>();
        sceneMap.put("scene_str", ShiroUtils.getUserId());
        Map<String, Object> actionInfoMap = new HashMap<>();
        actionInfoMap.put("scene", sceneMap);
        dataMap.put("action_info", actionInfoMap);

        Map<String, Object> reslutMap = wxReqDataPost(url, JSONObject.toJSONString(dataMap));

        if (reslutMap == null) {
            throw new SysException(SysExceptionCode.CODE_40038);
        } else {
            String ticket;
            try {
                ticket = URLEncoder.encode((String) reslutMap.get("ticket"), "GBK");
            } catch (UnsupportedEncodingException e) {
                throw new SysException(e.getMessage());
            }

            return getReqImgBase64("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket, null, "GET");
        }
    }

    private static String getReqImgBase64(String url, Map<String, Object> dataMap, String requestMethod) {
        HttpsURLConnection conn = null;
        PrintWriter out = null;
        InputStream in = null;
        ByteArrayOutputStream bos = null;
        String base64String = "";

        try {
            URL realUrl = new URL(url);
            conn = (HttpsURLConnection) realUrl.openConnection();// 打开和URL之间的连接
            conn.setRequestMethod(requestMethod); // 设置请求方式
            conn.setConnectTimeout(10000);// 超时设置,防止 网络异常的情况下,可能会导致程序僵死而不继续往下执行
            conn.setReadTimeout(3000);
            if (dataMap != null) {
                // 发送POST请求必须设置如下两行
                conn.setDoOutput(true);
                conn.setDoInput(true);
                out = new PrintWriter(conn.getOutputStream());// 获取URLConnection对象对应的输出流
                out.print(JSONObject.toJSONString(dataMap));// 发送请求参数
                out.flush();// flush输出流的缓冲
            }
            in = conn.getInputStream();  // 得到图片的二进制内容

            Image srcImg = ImageIO.read(in);// 将文件二进制流修改为图片流
            BufferedImage buffImg = new BufferedImage(430, 430, BufferedImage.TYPE_INT_RGB); // 构建图片流
            buffImg.getGraphics().drawImage(srcImg.getScaledInstance(430, 430, Image.SCALE_SMOOTH), 0, 0, null); //绘制改变尺寸后的图
            ByteArrayOutputStream os = new ByteArrayOutputStream();// 将图片流修改为文件二进制流
            ImageIO.write(buffImg, "png", os);
            in = new ByteArrayInputStream(os.toByteArray());
            buffImg.flush();// 刷新，将重置为类似于首次创建时的状态
            srcImg.flush();
            os.close();// 设null是告诉jvm此资源可以回收

            bos = new ByteArrayOutputStream();
            byte[] b1 = new byte[1024];
            int len;
            while ((len = in.read(b1)) != -1) {
                bos.write(b1, 0, len);
            }
            byte[] fileByte = bos.toByteArray();  // 转换为字节数组，方便转换成base64编码
            base64String = new BASE64Encoder().encode(fileByte);  // import org.apache.commons.codec.binary.Base64
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                //让系统回收资源，但不一定是回收刚才设成null的资源，可能是回收其他没用的资源。
                if (out != null) out.close();
                if (in != null) in.close();
                if (bos != null) bos.close();
                if (conn != null) conn.disconnect();
                System.gc();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (base64String == null || base64String.equals("")) {
            WeChatUtils.weChatTokenMap.remove(isType ? "xcx_access_token" : "gzh_access_token");
            throw new SysException(SysExceptionCode.CODE_40038);
        }

        return "data:image/png;base64," + base64String;
    }


    public static String getToken() {
        return getToken(false);
    }

    /**
     * @param isType 1是小程序请求
     * @return * * *
     * 属性 类型 说明
     * access_token	string	获取到的凭证
     * expires_in	number	凭证有效时间，单位：秒。目前是7200秒之内的值。
     * errcode	number	错误码
     * errmsg	string	错误信息
     * <p>
     * =====================================
     * <p>
     * errcode 的合法值
     * 值	说明	最低版本
     * -1	系统繁忙，此时请开发者稍候再试
     * 0	请求成功
     * 40001	AppSecret 错误或者 AppSecret 不属于这个小程序，请开发者确认 AppSecret 的正确性
     * 40002	请确保 grant_type 字段值为 client_credential
     * 40013	不合法的 AppID，请开发者检查 AppID 的正确性，避免异常字符，注意大小写
     */
    public static String getToken(boolean isType) {
        WeChatUtils.isType = isType;
        String token = null;

        /*JedisPoolUtil.set(WeChatUtils.accessTokenKey, token, 7000); // 微信的access_token有效期是7200秒，所以我们的过期时间要比微信的快token*/
        if (WeChatUtils.weChatTokenMap.containsKey(isType ? "xcx_date" : "gzh_date") && String.valueOf(WeChatUtils.weChatTokenMap.get(isType ? "xcx_access_token" : "gzh_access_token")) != null &&
                ((Long) WeChatUtils.weChatTokenMap.get(isType ? "xcx_date" : "gzh_date") + 7100) > (new Date().getTime() / 1000)) {
            token = (String) WeChatUtils.weChatTokenMap.get(isType ? "xcx_access_token" : "gzh_access_token");
        }
        if (token == null) {
            String url = "https://api.weixin.qq.com/cgi-bin/token?appid=" + WeChatUtils.getTasKeyMap().get(SysPlugin.appId) + "&secret=" + WeChatUtils.getTasKeyMap().get(SysPlugin.appSecret) + "&grant_type=" + clientCredential;
            if (isType) {
                url = "https://api.weixin.qq.com/cgi-bin/token?appid=" + WeChatUtils.getAppletKeyMap().get(SysPlugin.appId) + "&secret=" + WeChatUtils.getAppletKeyMap().get(SysPlugin.appSecret) + "&grant_type=" + clientCredential;
            }

            Map<String, Object> tokenMap = WeChatUtils.wxReqDataGet(url);
            if (tokenMap == null) throw new SysException(SysExceptionCode.CODE_40039);

            token = (String) tokenMap.get("access_token");

            WeChatUtils.weChatTokenMap.put(isType ? "xcx_date" : "gzh_date", new Date().getTime() / 1000);
            WeChatUtils.weChatTokenMap.put(isType ? "xcx_access_token" : "gzh_access_token", token);
        }

        if (token == null || token.equals("")) throw new SysException(SysExceptionCode.CODE_40039);

        return token;
    }


    public static Map<String, Object> getOpenIdAndSessionKey(String code) {
        return getOpenIdAndSessionKey(code, false);
    }

    /**
     * @param code string 是	登录时获取的 code
     * @return * * *
     * 属性	类型	说明
     * openid	string	用户唯一标识
     * session_key	string	会话密钥
     * unionid	string	用户在开放平台的唯一标识符，在满足 UnionID 下发条件的情况下会返回，详见 UnionID 机制说明。
     * errcode	number	错误码
     * errmsg	string	错误信息
     * <p>
     * ===================================
     * <p>
     * errcode 的合法值
     * 值	说明	最低版本
     * -1	系统繁忙，此时请开发者稍候再试
     * 0	请求成功
     * 40029	code 无效
     * 45011	频率限制，每个用户每分钟100次
     */
    public static Map<String, Object> getOpenIdAndSessionKey(String code, boolean isType) {
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + WeChatUtils.getTasKeyMap().get(SysPlugin.appId) + "&secret=" + WeChatUtils.getTasKeyMap().get(SysPlugin.appSecret) + "&grant_type=" + authorizationCode + "&js_code=" + code;
        if (isType) {
            url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + WeChatUtils.getAppletKeyMap().get(SysPlugin.appId) + "&secret=" + WeChatUtils.getAppletKeyMap().get(SysPlugin.appSecret) + "&grant_type=" + authorizationCode + "&js_code=" + code;
        }
        return WeChatUtils.wxReqDataGet(url);
    }


    /**
     * 微信Post请求
     * 如果微信端返回错误码或者没有返回数据，这个方法直接返回null
     * 该方法没有使用线程，可能会卡死
     *
     * @param url    请求的url
     * @param params
     * @return
     */
    public static Map<String, Object> wxReqDataPost(String url, String params) {
        Map<String, Object> resultMap = Utils.reqPostMap(url, params);
        if (resultMap == null) {
            throw new SysException("获取远程数据失败");
        } else if ((resultMap.containsKey("errcode") && (Integer) resultMap.get("errcode") != 0)) {
            String msg = String.valueOf(resultMap.get("errmsg"));
            if (Utils.formatInt(resultMap.get("errcode")) == 40001 && msg.matches(".*invalid credential, access_token is invalid or not latest hints.*")) {
                WeChatUtils.weChatTokenMap.remove(isType ? "xcx_access_token" : "gzh_access_token");
            }
            throw new SysException(resultMap.get("errcode") + ":" + resultMap.get("errmsg"));
        }
        return resultMap;
    }

    /**
     * 微信Get请求
     * 如果微信端返回错误码或者没有返回数据，这个方法直接返回null
     * 该方法没有使用线程，可能会卡死
     *
     * @param url 请求的url
     * @return
     */
    public static Map<String, Object> wxReqDataGet(String url) {
        Map<String, Object> resultMap = Utils.reqGetMap(url);
        if (resultMap == null) {
            throw new SysException("获取远程数据失败");
        } else if ((resultMap.containsKey("errcode") && (Integer) resultMap.get("errcode") != 0)) {
            String msg = String.valueOf(resultMap.get("errmsg"));
            if (Utils.formatInt(resultMap.get("errcode")) == 40001 && msg.matches(".*invalid credential, access_token is invalid or not latest hints.*")) {
                WeChatUtils.weChatTokenMap.remove(isType ? "xcx_access_token" : "gzh_access_token");
            }
            throw new SysException(resultMap.get("errcode") + ":" + resultMap.get("errmsg"));
        }
        return resultMap;
    }


    /**
     * 接收事件推送
     * <p>
     * 目录 MsgType Event
     * 1 关注/取消关注事件
     * 2 扫描带参数二维码事件
     * 3 上报地理位置事件
     * 4 自定义菜单事件
     * 5 点击菜单拉取消息时的事件推送
     * 6 点击菜单跳转链接时的事件推送
     * <p>
     * <p>
     * 》》》1.关注/取消关注事件《《《
     * 参数	描述
     * ToUserName	开发者微信号
     * FromUserName	发送方帐号（一个OpenID）
     * CreateTime	消息创建时间 （整型）
     * MsgType	消息类型，event
     * Event	事件类型，subscribe(订阅)、unsubscribe(取消订阅)
     * <p>
     * <p>
     * 》》》2.扫描带参数二维码事件《《《
     * 参数	描述
     * ToUserName	开发者微信号
     * FromUserName	发送方帐号（一个OpenID）
     * CreateTime	消息创建时间 （整型）
     * MsgType	消息类型，event
     * Event	事件类型，subscribe
     * EventKey	事件KEY值，qrscene_为前缀，后面为二维码的参数值
     * Ticket	二维码的ticket，可用来换取二维码图片
     * <p>
     * <p>
     * 》》》3.上报地理位置事件《《《
     * 参数	描述
     * ToUserName	开发者微信号
     * FromUserName	发送方帐号（一个OpenID）
     * CreateTime	消息创建时间 （整型）
     * MsgType	消息类型，event
     * Event	事件类型，LOCATION
     * Latitude	地理位置纬度
     * Longitude	地理位置经度
     * Precision	地理位置精度
     * <p>
     * <p>
     * 》》》5.点击菜单拉取消息时的事件推送《《《
     * 参数	描述
     * ToUserName	开发者微信号
     * FromUserName	发送方帐号（一个OpenID）
     * CreateTime	消息创建时间 （整型）
     * MsgType	消息类型，event
     * Event	事件类型，CLICK
     * EventKey	事件KEY值，与自定义菜单接口中KEY值对应
     * <p>
     * <p>
     * 》》》6.点击菜单跳转链接时的事件推送《《《
     * 参数	描述
     * ToUserName	开发者微信号
     * FromUserName	发送方帐号（一个OpenID）
     * CreateTime	消息创建时间 （整型）
     * MsgType	消息类型，event
     * Event	事件类型，VIEW
     * EventKey	事件KEY值，设置的跳转URL
     * <p>
     * <p>
     * MsgType
     * 消息类型，接收事件推送为event
     * 消息类型，文本为text
     * 消息类型，图片为image
     * 消息类型，语音为voice
     * 消息类型，视频为video
     * 消息类型，音乐为music
     * 消息类型，图文为news
     *
     * @param domSource
     * @return
     * @throws TransformerException
     */

    public static String receiveEventPush(DOMSource domSource) {
        String fromUserName = null;
        String toUserName = null;
        String text = null;
        try {
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);

            Map<String, Object> dataMap = JSONObject.parseObject(XML.toJSONObject(writer.toString()).toString(), Map.class);
            Map<String, Object> xmlMap = (Map<String, Object>) dataMap.get("xml");

            String msgType = (String) xmlMap.get("MsgType");
            fromUserName = (String) xmlMap.get("FromUserName");
            toUserName = (String) xmlMap.get("ToUserName");
            String event = (String) xmlMap.get("Event");
            if (msgType.equals("event")) {
                if (event.equals("subscribe")) {
                    // 关注事件
                    text = "感谢您的关注";
                    String eventKey = (String) xmlMap.get("EventKey");
                    if (eventKey != null && !eventKey.equals("")) {
                        String userId = String.valueOf(eventKey.substring(eventKey.indexOf("_") + 1));
                        SysUser sysUser = sysUserService.get(userId);
                        if (sysUser != null) {
                            sysUser.setTasOpenid((String) xmlMap.get("FromUserName"));
                            sysUserService.update(sysUser);
                            text += "，已成功绑定账号：" + sysUser.getUserName();
                        } else {
                            text = "，绑定账号失败，账号不存在或者未登陆";
                        }
                    }
                    text += "，如需帮助请联系客服";
                } else if (event.equals("unsubscribe")) {
                    // 取消关注事件
                    text = "再见，感谢您的使用，如有需要可以再次关注我们哦";
                } else if (event.equals("SCAN")) {
                    // text = "扫描二维码带参数事件";
                    String userId = String.valueOf(xmlMap.get("EventKey"));
                    SysUser sysUser = sysUserService.get(userId);
                    if (sysUser != null) {
                        sysUser.setTasOpenid((String) xmlMap.get("FromUserName"));
                        sysUserService.update(sysUser);
                        text = "已成功绑定账号：" + sysUser.getUserName();
                    } else {
                        text = "绑定账号失败，账号不存在或者未登陆";
                    }
                } else {
                    text = "对不起，无法识别消息类型，如需帮助请联系客服";
                }
            } else if (msgType.equals("text")) {
                // 消息类型，为text
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            } else if (msgType.equals("image")) {
                // 消息类型，图片为image
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            } else if (msgType.equals("voice")) {
                // 消息类型，语音为voice
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            } else if (msgType.equals("video")) {
                // 消息类型，视频为video
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            } else if (msgType.equals("music")) {
                // 消息类型，音乐为music
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            } else if (msgType.equals("news")) {
                // 消息类型，图文为news
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            } else {
                text = "对不起，无法识别消息类型，如需帮助请联系客服";
            }

            /*Stringa returnData = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
                    "    <xml>" +
                    "        <Content>" + text + "</Content>" +
                    "        <ToUserName>" + fromUserName + "</ToUserName>" +
                    "        <FromUserName>" + toUserName + "</FromUserName>" +
                    "        <CreateTime>" + new Date().getTime() / 1000 + "</CreateTime>" +
                    "        <MsgType>text</MsgType>" +
                    "    </xml>";

            //输出格式化后的json
            return returnData;*/
        } catch (Exception e) {
            Utils.loggerError(WeChatUtils.class, e);
            text = "系统出现错误，请稍候重试吧";
        } finally {
            String returnData = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" +
                    "    <xml>" +
                    "        <Content>" + text + "</Content>" +
                    "        <ToUserName>" + fromUserName + "</ToUserName>" +
                    "        <FromUserName>" + toUserName + "</FromUserName>" +
                    "        <CreateTime>" + new Date().getTime() / 1000 + "</CreateTime>" +
                    "        <MsgType>text</MsgType>" +
                    "    </xml>";
            return returnData;
        }
    }


    /**
     * 验证微信绑定服务器的方法
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        System.out.println(signature + "_" + timestamp + "_" + nonce);

        //1.定义数组存放tooken，timestamp,nonce
        String[] arr = {"7i6L5SEu4NPuYiGVAXMy0ZnFxd6", timestamp, nonce};

        //2.对数组进行排序
        Arrays.sort(arr);

        //3.生成字符串
        StringBuffer sb = new StringBuffer();
        for (String s : arr) {
            sb.append(s);
        }

        //4.sha1加密,网上均有现成代码
        String temp = getSha(sb.toString());

        //5.将加密后的字符串，与微信传来的加密签名比较，返回结果
        return signature.equals(temp);
    }


    public static String getSha(String str) {
        if (!Utils.isBlank(str)) {
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;

            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            Utils.loggerError(WeChatUtils.class, e);
            return null;
        }
    }

    /**
     * 登录方法，保存token到redis
     *
     * @param code string 是	登录时获取的 code
     * @return
     */
    public static ResponseResult login(String code, Long userId, String url) {
        Map<String, Object> appIdMap = WeChatUtils.getOpenIdAndSessionKey(code, true);
        int resultCode;

        Map<String, Object> tokenMap = new HashMap<>();
        SysUser sysUser;
        if (userId != null && userId != 0) {
            sysUser = sysUserService.get(userId);
            if (sysUser.getAppletOpenid() == null || sysUser.getAppletOpenid().equals("")) {
                // 如果没有绑定openId就绑定
                sysUser.setAppletOpenid(String.valueOf(appIdMap.get("openid")));
                sysUserService.update(sysUser);
            }
        } else {
            QueryFilter queryFilter = new QueryFilter();
            queryFilter.and("appletOpenid", appIdMap.get("openid"));
            sysUser = sysUserService.find(queryFilter);
        }

        if (sysUser != null) {
            Subject subject = SecurityUtils.getSubject();
            // UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(sysUser.getLoginCode(), sysUser.getPassword());
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(sysUser.getLoginCode(), "WeChat:?/ptw789NoPassword..");
            subject.login(usernamePasswordToken);

            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Map<String, String> cookieMap = new HashMap<>();
            cookieMap.put("userAgent", request.getHeader("user-agent"));
            cookieMap.put("remoteAddr", request.getRemoteAddr());
            cookieMap.put("uuid", UUID.randomUUID().toString());
            cookieMap.put("sessionId", ShiroUtils.getSessionId());
            String access_token = AesCBCUtil.encrypt(AesCBCUtil.encrypt(JSONObject.toJSONString(cookieMap)));

            tokenMap.put("accessToken", access_token);
            tokenMap.put("resPersonalId", sysUser.getSysPersonalId());
            tokenMap.put("userId", sysUser.getUserId());
            tokenMap.put("loginCode", sysUser.getLoginCode());
            tokenMap.put("userName", sysUser.getUserName());
            if (url != null && !url.equals("")) {
                tokenMap.put("url", url);
            }

            resultCode = SysExceptionCode.CODE_10001.getCode();
            // tokenMap.put("menuList", this.getMenuList(sysUser.getId()));
        } else {
            String token = UUID.randomUUID().toString();
            tokenMap.put("accessToken", token);
            accessTokenMap.put(token, (String) appIdMap.get("openid"));
            resultCode = SysExceptionCode.CODE_40041.getCode();
        }

        return ResponseResult.printMapJson(resultCode, tokenMap);
    }

    /**
     * 绑定账号方法
     *
     * @param json
     * @return
     */
    public static ResponseResult bind(String json) {
        String appletOpenid = null;
        String accessToken = Utils.getAccessToken();
        if (accessTokenMap.containsKey(accessToken)) {
            appletOpenid = accessTokenMap.get(accessToken);
            accessTokenMap.remove(accessToken);
        }


        SysUser sysUser = sysUserService.login(json, null, null);
        if (sysUser == null) throw new SysException(SysExceptionCode.CODE_40006);

        if (sysUser.getAppletOpenid() != null && !sysUser.getAppletOpenid().equals("")) {
            throw new SysException(SysExceptionCode.CODE_40042);
        } else if (appletOpenid != null) {
            sysUser.setAppletOpenid(appletOpenid);
            sysUserService.update(sysUser);

            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Map<String, String> cookieMap = new HashMap<>();
            cookieMap.put("userAgent", request.getHeader("user-agent"));
            cookieMap.put("remoteAddr", request.getRemoteAddr());
            cookieMap.put("uuid", UUID.randomUUID().toString());
            cookieMap.put("sessionId", ShiroUtils.getSessionId());
            String access_token = AesCBCUtil.encrypt(AesCBCUtil.encrypt(JSONObject.toJSONString(cookieMap)));

            Map<String, Object> resultDataMap = new HashMap<>();
            resultDataMap.put("accessToken", access_token);
            resultDataMap.put("resPersonalId", sysUser.getSysPersonalId());
            resultDataMap.put("userId", sysUser.getUserId());
            resultDataMap.put("loginCode", sysUser.getLoginCode());
            resultDataMap.put("userName", sysUser.getUserName());

            return ResponseResult.printMapJson(SysExceptionCode.CODE_10003.getCode(), resultDataMap);
        } else if (appletOpenid == null) {
            throw new SysException(SysExceptionCode.CODE_40043);
        } else {
            throw new SysException(SysExceptionCode.CODE_40001);
        }
    }
}
